package io.github.smileinmotion.oplog.aop;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.json.JSONUtil;
import io.github.smileinmotion.oplog.annotation.OpLogTag;
import io.github.smileinmotion.oplog.config.OpLogProperties;
import io.github.smileinmotion.oplog.function.OpLogParser;
import io.github.smileinmotion.oplog.model.OperLogInfo;
import io.github.smileinmotion.oplog.model.OperLog;
import io.github.smileinmotion.oplog.service.IOpLogCallService;
import io.github.smileinmotion.oplog.util.AppContextUtil;
import io.github.smileinmotion.oplog.util.IpUtils;
import io.github.smileinmotion.oplog.util.MethodParamUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.support.AopUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Log AOP
 *
 * @author Mr.spc
 * @date 2022/2/20 17:28
 */
@Aspect
public class OpLogAspect {

    @Resource(type = OpLogProperties.class)
    private OpLogProperties opLogProperties;

    @Resource(type = OpLogParser.class)
    private OpLogParser opLogParser;

    /**
     * 定义切点
     */
    @Pointcut("@annotation(io.github.smileinmotion.oplog.annotation.OpLogTag) || @annotation(io.github.smileinmotion.oplog.annotation.OpLogTags)")
    public void pointCut() {
    }

    /**
     * 环绕通知
     *
     * @param joinPoint joinPoint
     * @return Object
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String returnType = method.getReturnType().getName();
        Object[] args = joinPoint.getArgs();
        Object target = joinPoint.getTarget();
        Class<?> targetClass = AopUtils.getTargetClass(target);
        String methodName = joinPoint.getSignature().getName();
        OpLogTag[] opLogTags = method.getAnnotationsByType(OpLogTag.class);
        List<OperLog> operLogList = new ArrayList<>();
        for (OpLogTag opLogTag : opLogTags) {
            //注解参数解析
            OperLog operLog = parseLogAnnotation(opLogTag);
            //类方法信息
            operLog.setClassName(targetClass.getName());
            operLog.setMethodName(methodName);
            operLog.setExecuteResulType(returnType);
            //request 请求参数解析
            parseRequest(operLog);
            //设置参数
            operLog.setOrNormal(false);
            operLog.setMethodParam(MethodParamUtils.getMethodParam(args));
            operLog.setJoinPoint(joinPoint);

            operLogList.add(operLog);
        }
        //获取需要解析的数据
        List<String> expressTemplate = getExpressTemplate(operLogList);
        //模版解析
        Map<String, String> customFunctionExecResultMap = opLogParser.processBeforeExec(expressTemplate, method, args, targetClass);
        Object result = "";
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            operLogList.forEach(it -> {
                it.setOrNormal(true);
                it.setStack(Arrays.toString(e.getStackTrace()));
            });
            throw new Throwable(e);
        } finally {
            Map<String, String> templateMap = opLogParser.processAfterExec(expressTemplate, customFunctionExecResultMap, method, args, targetClass, result);
            sendLog(operLogList, result, templateMap);
        }

        return result;
    }

    /**
     * 发送日志
     *
     * @param opLogOpers  日志实体
     * @param result      返回值
     * @param templateMap 模版Map
     */
    private void sendLog(List<OperLog> opLogOpers, Object result, Map<String, String> templateMap) {
        //返回值赋值
        opLogOpers.forEach(logOps -> logOps.setExecuteResult(result));
        //操作实体返回
        List<OperLogInfo> operLogInfos = createOpLogInfo(templateMap, opLogOpers);
        IOpLogCallService callService = AppContextUtil.getApplicationContext().getBean(this.opLogProperties.getOpLogServiceImpl());
        //回调函数
        callService.record(operLogInfos);
    }

    /**
     * 创建操作日志实体
     *
     * @param templateMap
     * @param operLogList
     * @return List<OperLog>
     */
    private List<OperLogInfo> createOpLogInfo(Map<String, String> templateMap, List<OperLog> operLogList) {
        List<OperLogInfo> operLogInfos = new ArrayList<>();
        for (OperLog operLog : operLogList) {
            OperLogInfo operLogInfo = BeanUtil.toBean(operLog, OperLogInfo.class);
            //SpEL数据
            spelDataParsing(templateMap, operLogInfo);

            operLogInfo.setEndDate(new Date());
            operLogInfo.setPlatform(this.opLogProperties.getPlatform());
            operLogInfos.add(operLogInfo);
        }

        return operLogInfos;
    }

    /**
     * SpEL数据 解析
     *
     * @param templateMap spel map 数据
     * @param operLogInfo 日志实体数据
     */
    private void spelDataParsing(Map<String, String> templateMap, OperLogInfo operLogInfo) {
        IOpLogCallService callService = AppContextUtil.getApplicationContext().getBean(opLogProperties.getOpLogServiceImpl());

        String tenant = templateMap.get(operLogInfo.getTenant());
        if (StrUtil.isBlank(tenant)) {
            tenant = operLogInfo.getTenant();
        }
        if (StrUtil.isBlank(tenant)) {
            tenant = callService.getTenant();
        }

        String operator = templateMap.get(operLogInfo.getOperator());
        if (StrUtil.isBlank(operator)) {
            operator = operLogInfo.getOperator();
        }
        if (StrUtil.isBlank(operator)) {
            tenant = callService.getOperator();
        }

        String customMsg = templateMap.get(operLogInfo.getCustomMsg());
        if (StrUtil.isBlank(customMsg)) {
            customMsg = operLogInfo.getCustomMsg();
        }
        String customTag = templateMap.get(operLogInfo.getCustomTag());
        if (StrUtil.isBlank(customTag)) {
            customTag = operLogInfo.getCustomTag();
        }
        operLogInfo.setCustomMsg(customMsg);
        operLogInfo.setCustomMsg(customTag);
        operLogInfo.setTenant(tenant);
        operLogInfo.setOperator(operator);
    }


    /**
     * 将注解转为实体
     *
     * @param opLogTag 注解
     * @return 实体
     */
    private OperLog parseLogAnnotation(OpLogTag opLogTag) {
        OperLog operLogOps = new OperLog();
        operLogOps.setBeginDate(new Date());
        operLogOps.setModuleName(opLogTag.moduleName());
        operLogOps.setMenuName(opLogTag.menuName());
        operLogOps.setOperaType(opLogTag.operaType());
        operLogOps.setBizId(opLogTag.bizId());
        operLogOps.setCustomMsg(opLogTag.customMsg());
        operLogOps.setCustomTag(opLogTag.customTag());
        operLogOps.setTenant(opLogTag.tenant());
        operLogOps.setOperator(opLogTag.operator());
        return operLogOps;
    }

    /**
     * 将 Request 参数赋值实体对象
     *
     * @param operLog 注解
     */
    private void parseRequest(OperLog operLog) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (!ObjectUtils.isEmpty(attributes)) {
            HttpServletRequest request = attributes.getRequest();
            operLog.setRequestSource("external");
            operLog.setRequestParam(JSONUtil.toJsonStr(getRequestParameter(request)));
            operLog.setIpAddress(IpUtils.getIp(request));
            operLog.setHttpUri(request.getRequestURI());
            operLog.setHttpMethod(request.getMethod());
            operLog.setContentType(request.getContentType());
            operLog.setDeviceType(JSONUtil.toJsonStr(UserAgentUtil.parse(request.getHeader("User-Agent"))));
        }
    }

    /**
     * 获取 request 参数
     *
     * @param request request
     * @return Map<String, Object>
     */
    private Map<String, Object> getRequestParameter(HttpServletRequest request) {
        Map<String, Object> originalParameter = new HashMap<>();
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (String parameter : parameterMap.keySet()) {
            String[] strings = parameterMap.get(parameter);
            originalParameter.put(parameter, Optional.ofNullable(strings).orElse(new String[0]));
        }
        return originalParameter;
    }

    /**
     * 获取不为空的 自定义函数 解析模板
     *
     * @param operLogList 数据
     * @return Set
     */
    private List<String> getExpressTemplate(List<OperLog> operLogList) {
        Set<String> set = new HashSet<>();
        for (OperLog operLog : operLogList) {
            set.addAll(Arrays.asList(operLog.getBizId(), operLog.getTenant(), operLog.getOperator(), operLog.getCustomMsg()));
        }
        return set.stream().filter(customFunction -> !StrUtil.isBlank(customFunction)).collect(Collectors.toList());
    }
}
